# rays are the catch-all name for mad science energy stuff that you direct at people
# to cause harm or some specific effect.
# in this game, "prisms" generate rays, and "projectors" project them.
# in this game, rays might be projected in many different ways:
#	narrow-angle, wide-angle, 360-degree (unconventional use of the word "ray")
#	beam that bounces off the walls..
# generally, projectors that project more widely diminish the effect somewhat, but
# are also more advanced devices.

# Ideas for rays!
#	heat ray: good old-fashioned damage
#	attractor ray / repeller ray: likely the latter would be most useful (in handheld devices, anyway -- attractor grenades could be nice enough)
#	freeze ray: this is an obvious one (it's not an ice ray)
#	death ray: should include this for some highly destructive high-level ray. (problem with the natural definition: enemies are robots)
#	haywire ray: confuses robots
#	reprogramming ray: makes robots friendly

import random
import math
from rlcore import *

from rlfov import Fov, Wheel, WheelIndex

from rlcore import *

from erlglobals import *

class Effect:
	def __init__(self, effectType, name, power ):
		self.effectType = effectType
		self.name = name
		self.power = power
	def affect(self, mobile):
		pass
	# alternatively, define (in subclass):
	#   affectLevel( self, level, origin, area )
	# _instead_ for things like attractors/repellers
	def getColourAt(self, p, origin ):
		if self.colouring == "uniform":
			return self.colour
		elif self.colouring == "random":
			return random.choice( self.colours )
		return (128,128,128)


class HeatEffect (Effect):
	def __init__(self):
		Effect.__init__( self, "ray", "heat", 1.0 )
		self.colouring = "uniform"
#		self.colours = [ (200+i,32+j,32+k) for i in range(-10,10) for j in range(-10,10) for k in range(-10,10) ]
		# random colouring turns out to be a bad idea with my engine, which is slow to draw sudden new palettes
		# suddenly
		self.colour = (200,32,32)
	def affect(self, mobile):
		amp = int( 0.5 + self.power * 15 )
		mobile.damage( randomRoll( amp ) )
isAllowedPointer( HeatEffect )

class CannonBall (Effect):
	def __init__(self):
		Effect.__init__( self, "ray", "cannon ball", 1.0 )
		self.colouring = "uniform"
		self.colour = (0,0,0)
	def affect(self, mobile):
		amp = int( 0.5 + self.power * 20 )
		mobile.damage( randomRoll( amp ) )
isAllowedPointer( CannonBall )

class FreezeEffect (Effect):
	def __init__(self):
		Effect.__init__( self, "ray", "freeze", 1.0 )
		self.colouring = "uniform"
		self.colour = (153,255,255)
	def affect(self, mobile):
		amp = max( 1, int( self.power * 5.0 ) )
		if mobile.frozenCountdown == 0:
			# you can't "refresh" freezes; this would
			# be very frustrating for the player.
			mobile.frozenCountdown = amp
isAllowedPointer( FreezeEffect )
	
class EffectProjector:
	def __init__(self, interface, level, effect, origin):
		self.interface = interface
		self.visionMask = interface.elements["map"].visionMask
		self.level = level
		self.effect = effect
		self.origin = origin
		self.area = [] # subclass should fill this with positions
	def project(self):
		if self.interface.elements["map"].mobile.level == self.level:
			self.display()
		self.apply()
	def apply(self):
		affectLevel = getattr( self.effect, "affectLevel", None )
		if not affectLevel:
			for x, y in self.area:
				mob = self.level.tiles[x][y].mobile
				if mob:
					self.effect.affect( mob )
		else:
			affectLevel( self.effect, self.level, self.origin, self.area )
	def display(self):
		pass # flashy graphical display
	def displayBriefFlash(self, area, delay = 100):
		drew = False
		for x, y in area:
			if not self.visionMask.isLit( x,y ): continue
			g = Glyph( ' ', (0,0,0), self.effect.getColourAt( (x,y), self.origin ) )
			self.interface.move( x, y )
			self.interface.put( g )
			drew = True
		if drew:
			self.interface.redraw()
			if delay > 0:
				self.interface.delay( delay )
		self.interface.refresh()
	def displayFromOrigin(self, cells):
		x0, y0 = self.origin
		cells = sorted( [ ((x-x0)*(x-x0)+(y-y0)*(y-y0),(x,y) ) for (x, y) in cells if self.visionMask.isLit( x, y ) ] )
		i = 0
		area = []
		if not cells: return
		drew, everDrew = False, False
		for r in range( int( math.ceil( math.sqrt( cells[-1][0] ) ) ) + 1 ):
			while i < len(cells) and cells[i][0] <= r*r:
				x, y = cells[i][1]
				g = Glyph( ' ', (0,0,0), self.effect.getColourAt( (x,y), self.origin ) )
				self.interface.move( x, y )
				self.interface.put( g )
				everDrew = drew = True
				i += 1
			if drew:
				self.interface.redraw()
				self.interface.delay( 10 )
				drew = False
		if everDrew:
			self.interface.delay( 50 )
			self.interface.refresh()
		self.interface.clearRepeats()
	def displaySuccessively(self, cellList, cumulative = False, asGlyph = None ):
		cellsVisible = [ True for (x, y) in cellList if self.visionMask.isLit( x, y ) ]
		if not cellsVisible: return
		for x, y in cellList:
			if self.visionMask.isLit( x, y ):
				if asGlyph:
					g = self.interface.mergeGlyph( asGlyph, x, y )
				else:
					g = Glyph( ' ', (0,0,0), self.effect.getColourAt( (x,y), self.origin ) )
				self.interface.move( x, y )
				self.interface.put( g )
			self.interface.redraw()
			if not cumulative:
				self.interface.delay( 10 )
				self.interface.refresh()
			else:
				self.interface.delay( 10 )
		if cumulative:
			self.interface.delay( 100 )
		self.interface.refresh()
		self.interface.clearRepeats()
	def addFromLof( self, lof ):
		for x in range(self.level.width):
			for y in range(self.level.height):
				if lof.isLit( x, y ) and (x,y) != self.origin and self.level.tiles[x][y].type.obstruction == "clear":
					self.area.append( (x,y) )
		
class EffectProjectorBouncerLine (EffectProjector):
	def __init__(self, interface, level, effect, origin, direction, radius ):
		EffectProjector.__init__(self, interface, level, effect, origin )
		x, y = origin
		dx, dy = direction
		self.noBounces = 0
		for i in range(radius):
			nx, ny = x + dx, y + dy
			obstruct = self.level.tiles[nx][ny].type.obstruction
			if obstruct != "clear":
				if not self.bounce(): return
				xWorks = self.level.tiles[x+dx][y].type.obstruction == "clear"
				yWorks = self.level.tiles[x][y+dy].type.obstruction == "clear"
				if (xWorks and yWorks) or not (xWorks or yWorks):
					dx, dy = -dx, -dy
				elif xWorks:
					dy = -dy
					x += dx
				else:
					dx = -dx
					y += dy
			else:
				x, y = nx, ny
			assert self.level.tiles[x][y].type.obstruction == "clear"
			self.area.append( (x,y) )
			if self.mustStop( x, y ): return
	def bounce(self):
		self.noBounces += 1
		return True
	def mustStop(self, x, y):
		return False
	def display(self):
		self.displaySuccessively( self.area, cumulative = True )
isAllowedPointer( EffectProjectorBouncerLine )

class EffectProjectorBouncerBall (EffectProjectorBouncerLine):
	def __init__(self, interface, level, effect, origin, direction, radius ):
		EffectProjectorBouncerLine.__init__(self,interface,level,effect,origin,direction,radius)
	def mustStop(self, x, y):
		return self.level.tiles[x][y].mobile != None
	def display(self):
		self.displaySuccessively( self.area, cumulative = False )
	def displayGlyph(self, glyph):
		self.displaySuccessively( self.area, cumulative = False, asGlyph=glyph)
isAllowedPointer( EffectProjectorBouncerBall )

class EffectProjectorBouncerBallLong (EffectProjectorBouncerBall):
	def __init__(self, interface, level, effect, origin, direction):
		radius = 10
		EffectProjectorBouncerBall.__init__(self, interface, level, effect, origin, direction, radius)
isAllowedPointer( EffectProjectorBouncerBallLong )
		
	

				
#####################
#\    /\
# \  /
#
# /
#/
#\

class EffectProjectorLine (EffectProjector): # simple line; passes through things, stops at first wall.
	def __init__(self, interface, level, effect, origin, direction):
		EffectProjector.__init__(self, interface, level, effect, origin )
		x, y = origin
		dx, dy = direction
		while True:
			x += dx
			y += dy
			obstruct = self.level.tiles[x][y].type.obstruction
			if obstruct != "clear":
				break
			self.area.append( (x,y) )
	def display(self):
		self.displayFromOrigin( self.area )
isAllowedPointer( EffectProjectorLine )

class EffectProjectorWideAngle (EffectProjector):
	def __init__(self, interface, level, effect, origin, direction, size, radius = None):
		EffectProjector.__init__(self, interface, level, effect, origin )
		# the "performance hit" from trig approximations really,
		# really shouldn't matter here.
		extent = size / 2.0
		from math import atan2, sin, cos, pi
		x, y = self.origin
		dx, dy = direction
		dy *= -1
		u = atan2( dy, dx )
		du = pi/16.0
		a0 = int(1000*cos(u-du)), -int(1000*sin(u-du))
		a1 = int(1000*cos(u+du)), -int(1000*sin(u+du))
		vision = IndependentVision( self.level, self.origin, [ "clear" ], radius )
		lof = Fov( vision )
		lof.initializeSector( x, y, a0, a1 )
		lof.spread()
		self.addFromLof( lof )
	def display(self):
		self.displayFromOrigin( self.area )
isAllowedPointer( EffectProjectorWideAngle )

class EffectProjectorWideAngleStandard (EffectProjectorWideAngle):
	def __init__(self, interface, level, effect, origin, direction, radius = None ):
		from math import pi
		size = pi/3.0
		EffectProjectorWideAngle.__init__( self, interface, level, effect, origin, direction, radius=radius, size=size)
isAllowedPointer( EffectProjectorWideAngleStandard )

class EffectProjectorWideAngleLarge (EffectProjectorWideAngle):
	def __init__(self, interface, level, effect, origin, direction, radius = None ):
		from math import pi
		size = pi/1.5
		EffectProjectorWideAngle.__init__( self, interface, level, effect, origin, direction, radius=radius, size=size)
isAllowedPointer( EffectProjectorWideAngleStandard )

class EffectProjectorRadial ( EffectProjector ):
	def __init__(self, interface, level, effect, origin, radius ):
		self.radius = radius
		EffectProjector.__init__(self, interface, level, effect, origin )
		vision = IndependentVision( self.level, self.origin, [ "clear" ], radius )
		lof = Fov( vision )
		lof.initialize360( self.origin[0], self.origin[1] )
		lof.spread()
		self.addFromLof( lof )
	def display(self):
		from math import cos, sin, pi, ceil, floor
		x0, y0 = self.origin
		for i in range(1,self.radius+1):
			area = set()
			n = 200
			for j in range(n):
				a = j/(2*pi)
				x, y = i*cos(a), i*sin(a)
				xt, yt = int(ceil(x)), int(ceil(y))
				xb, yb = int(floor(x)), int(floor(y))
				l = [ (x0+dx,y0+dy) for dx in (xt,xb) for dy in (yt,yb) ]
				for x,y in l:
					if (x,y) in self.area: area.add( (x,y) )
			self.displayBriefFlash( area, delay = 10 )
		self.interface.clearRepeats()
isAllowedPointer( EffectProjectorRadial )

class EffectProjectorRadialShort ( EffectProjectorRadial ):
	def __init__(self, interface, level, effect, origin ):
		radius = 3
		EffectProjectorRadial.__init__( self, interface, level, effect, origin, radius )
isAllowedPointer( EffectProjectorRadialShort )

class EffectProjectorRadialMedium ( EffectProjectorRadial ):
	def __init__(self, interface, level, effect, origin ):
		radius = 5
		EffectProjectorRadial.__init__( self, interface, level, effect, origin, radius )
isAllowedPointer( EffectProjectorRadialMedium )
